﻿using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using XDL.Framework;
using XDL.Framework.Graphics;
using XDL.Framework.Collections;

namespace XDL.Framework.Particles
{
    /// <summary>
    /// Line trail system
    /// </summary>
    /// <remarks>
    /// Provides also support for the gun fire effect.
    /// </remarks>
    public abstract class LineTrailSystem : 
       DrawableGameComponent
    {
        DynamicVertexBuffer m_lineTrailVertexBuffer;
        LineTrailVertex[] m_trailVertices;
        IndexBuffer m_trailIndexBuffer;

        //
        Effect m_effect;
        EffectParameter m_effectViewParameter;
        EffectParameter m_effectProjectionParameter;
        EffectParameter m_effectCameraPosition;

        //
        static Random m_rnd = new Random();

        //
        int m_activeVertices = 0;

        LineTrailSettings m_settings = new LineTrailSettings();

        /// <summary>
        /// Private constructor (singleton)
        /// </summary>
        /// <param name="game">The current game.</param>
        public LineTrailSystem(Game game)
            :
            base(game)
        {
        }

        /// <summary>
        /// Derived trail system classes should override this method
        /// and use it to initalize their tweakable settings.
        /// </summary>
        protected abstract void InitializeSettings(LineTrailSettings settings);

        public override void Initialize()
        {
            base.Initialize();

            InitializeSettings(m_settings);

            // helper
            GraphicsDevice gd = Game.GraphicsDevice;

            // 4 times the number of lines (4 points per quad!!)
            m_trailVertices = new LineTrailVertex[m_settings.MaxLines * 4];

            Effect effect = Game.Content.Load<Effect>("Shaders/LineTrailEffect");
            // make sure each line system has its own shader!
            m_effect = effect.Clone();
            // misc parameters
            Texture2D tex = Game.Content.Load<Texture2D>(m_settings.TextureName);
            m_effect.Parameters["BaseTexture"].SetValue(tex);
            m_effect.Parameters["Duration"].SetValue((float)m_settings.Duration.TotalSeconds);
            m_effect.Parameters["StartColor"].SetValue(m_settings.StartColor.ToVector4());
            m_effect.Parameters["EndColor"].SetValue(m_settings.EndColor.ToVector4());
            m_effect.Parameters["StartSize"].SetValue(m_settings.StartSize);
            m_effect.Parameters["EndSize"].SetValue(m_settings.EndSize);
            // direct access to often accessed effect parameters
            m_effectViewParameter = m_effect.Parameters["View"];
            m_effectProjectionParameter = m_effect.Parameters["Projection"];
            m_effectCameraPosition = m_effect.Parameters["CameraPosition"];
            // create the buffer object
            m_lineTrailVertexBuffer = new DynamicVertexBuffer(
                    gd,
                    LineTrailVertex.VertexDeclaration,
                    m_settings.MaxLines * 4,
                    BufferUsage.WriteOnly);

            // Create and populate the index buffer.
            ushort[] indices = new ushort[m_settings.MaxLines * 6];

            for (int i = 0; i < m_settings.MaxLines; i++)
            {
                indices[i * 6 + 0] = (ushort)(i * 4 + 0);
                indices[i * 6 + 1] = (ushort)(i * 4 + 1);
                indices[i * 6 + 2] = (ushort)(i * 4 + 2);

                indices[i * 6 + 3] = (ushort)(i * 4 + 0);
                indices[i * 6 + 4] = (ushort)(i * 4 + 2);
                indices[i * 6 + 5] = (ushort)(i * 4 + 3);
            }

            m_trailIndexBuffer = new IndexBuffer(gd, typeof(ushort), indices.Length, BufferUsage.WriteOnly);
            m_trailIndexBuffer.SetData(indices);        
        }

        public override void  Update(GameTime gameTime)
        {
            float dt = (float)gameTime.ElapsedGameTime.TotalSeconds;
            int n = m_activeVertices;
            m_activeVertices = 0;
            for (int i = 0; i < n; i++)
            {
                m_trailVertices[i].Lifetime -= dt;
                // still active?
                if (m_trailVertices[i].Lifetime > 0)
                {
                    // re-activate the TracerBullet
                    if (m_activeVertices != i)
                        m_trailVertices[m_activeVertices] = m_trailVertices[i];

                    m_activeVertices++;
                }
            }
        }

        /// <summary>
        /// Sets the camera view and projection matrices
        /// that will be used to draw this particle system.
        /// </summary>
        public void SetCamera(Matrix view, Matrix projection)
        {
            m_effectViewParameter.SetValue(view);
            m_effectProjectionParameter.SetValue(projection);
            m_effectCameraPosition.SetValue(view.Translation);
        }

        public override void  Draw(GameTime gameTime)
        {
            // nothing to do?
            if (m_activeVertices == 0)
                return;
#if DEBUG
            // make sure we have an even number of bullets
            System.Diagnostics.Debug.Assert(m_activeVertices % 4 == 0);
#endif
            // helper
            GraphicsDevice gd = Game.GraphicsDevice;

            m_lineTrailVertexBuffer.SetData(m_trailVertices);
            gd.SetVertexBuffer(m_lineTrailVertexBuffer);
            gd.Indices = m_trailIndexBuffer;

            // state
            gd.RasterizerState = RasterizerState.CullNone;
            /*
            gd.RenderState.DepthBufferWriteEnable = false;
            gd.RenderState.AlphaBlendEnable = true;
            gd.RenderState.AlphaBlendOperation = BlendFunction.Add;
            gd.RenderState.SourceBlend = Blend.SourceAlpha;
            gd.RenderState.DestinationBlend = Blend.One;
            */

            foreach (EffectPass pass in m_effect.CurrentTechnique.Passes)
            {
                pass.Apply();

                gd.DrawIndexedPrimitives(
                    PrimitiveType.TriangleList, 0,
                    0, m_activeVertices * 4,
                    0, m_activeVertices * 2);
            }

            // restore state
            gd.RasterizerState = RasterizerState.CullCounterClockwise;
            /*
            gd.RenderState.DepthBufferWriteEnable = true;
            gd.RenderState.AlphaBlendEnable = false;
             * */
        }

        /// <summary>
        /// Add a trail item
        /// </summary>
        /// <param name="start">Spawn position</param>
        /// <param name="end">End position</param>
        /// <param name="direction">Bullet direction</param>
        public void AddLine(Vector3 start, Vector3 end, Vector3 direction)
        {
            //
            // System.Diagnostics.Debug.Assert(m_activeTracerBulletCount < m_trailVertices.Length - 1);

            // make sure we can add 4 tracer bullets (for a quad)
            if (m_activeVertices < m_trailVertices.Length - 4)
            {
                LineTrailVertex elt = new LineTrailVertex();
                elt.Position = start;
                elt.Direction = direction;
                elt.Texcoord = Vector2.Zero;
                elt.Lifetime = (float)m_settings.Duration.TotalSeconds;

                elt.Texcoord.X = 0;
                elt.Texcoord.Y = 0;
                m_trailVertices[m_activeVertices] = elt;
                elt.Texcoord.Y = 1;
                m_trailVertices[m_activeVertices + 3] = elt;
                // tip of the trail
                elt.Position = end;
                //
                elt.Texcoord.X = 1;
                elt.Texcoord.Y = 0;
                m_trailVertices[m_activeVertices + 1] = elt;
                elt.Texcoord.Y = 1;
                m_trailVertices[m_activeVertices + 2] = elt;

                //
                m_activeVertices += 4;
            }
        }
    }
}
